﻿using System;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Imaging;

namespace RayDen.Library.Components.Surface.Spectral
{
    [Serializable]
    public class GlassMaterial : SpectralBrdf
    {
        public override BrdfType Type
        {
            get { return BrdfType.Specular; }
        }

        public GlassMaterial(RgbSpectrum refl, RgbSpectrum refrct, float outsideIorFact,
            float iorFact, bool reflSpecularBounce, bool transSpecularBounce)
        {
            
            Krefl = ColorFactory.CreateRegularSpd(ref refl, SpectrumType.Reflectance);
            Krefrct = ColorFactory.CreateRegularSpd(ref refrct, SpectrumType.Reflectance);
            ousideIor = outsideIorFact;
            ior = iorFact;

            reflectionSpecularBounce = reflSpecularBounce;
            transmitionSpecularBounce = transSpecularBounce;

            float nc = ousideIor;
            float nt = ior;
            float a = nt - nc;
            float b = nt + nc;
            R0 = a * a / (b * b);
        }

        public override bool IsDiffuse() { return false; }
        public override bool IsSpecular() { return true; }

   

        private SpectralDistribution Krefl, Krefrct;
        public float ousideIor = 1.0004f, ior = 1.5f;
        private float R0;
        public bool reflectionSpecularBounce, transmitionSpecularBounce;

        public override void F(ref Vector wo, ref Vector wi, ref Normal N, float lambda, out float fs)
        {
            throw new NotSupportedException("Internal error, called GlassMaterial::f()");
        }

        public override void Sample_f(ref Vector wo, ref Normal geoNormal, ref Normal shadeNormal, float lambda, ref SurfaceTextureData surfaceData, float u0, float u1, float u2, out BsdfSampleData result)
        {
            //Assert.IsTrue(!Krefl.IsBlack());
            //Assert.IsTrue(!Krefrct.IsBlack());
             
            result = new BsdfSampleData();
            Vector rayDir = -wo;
            var N = shadeNormal;
            var shadeN = shadeNormal;
            var wi = new Vector();

            var N1 = N.ToVec();
            var reflDir = rayDir - (2f * (Normal.Dot(ref N, ref rayDir))) * N1;

            // Ray from outside going in ?
            bool into = ((Normal.Dot(ref N, ref shadeN)) > 0);

            float nc = ousideIor;
            float nt = ior;
            float nnt = into ? (nc / nt) : (nt / nc);
            float ddn = (rayDir & shadeN.ToVec());
            float cos2t = 1f - nnt * nnt * (1f - ddn * ddn);
            result.F = RgbSpectrum.UnitSpectrum();
            // Total internal reflection
            if (cos2t < 0f)
            {
                wi = reflDir;
                result.Pdf = 1f;
                result.Type = reflectionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                //result.F = Krefl;
                result.Lambda = Krefl.Sample(lambda);
                result.Wi = wi;
                return;
            }

            float kk = (into ? 1f : -1f) * (ddn * nnt + MathLab.Sqrt((cos2t)));
            Vector nkk = kk * N1;
            Vector transDir = (nnt * rayDir - nkk).Normalize();

            float c = 1f - (into ? -ddn : (transDir & N1));

            float Re = R0 + (1f - R0) * c * c * c * c * c;
            float Tr = 1f - Re;
            float P = .25f + .5f * Re;

            if (Tr.NearEqual(0f))
            {
                if (Re.NearEqual(0f))
                {
                    result.Pdf = 0f;
                    result.Type = BrdfType.Specular;
                    result.F = new RgbSpectrum(0f);
                }
                else
                {
                    (wi) = reflDir;
                    result.Pdf = 1f;
                    result.Type = reflectionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                    result.Lambda = Krefl.Sample(lambda);

                }
            }
            else if (Re.NearEqual(0f))
            {
                (wi) = transDir;

                result.Pdf = 1f;
                result.Type = transmitionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                result.Lambda = Krefrct.Sample(lambda);


            }
            else if (u0 < P)
            {
                (wi) = reflDir;
                result.Pdf = P / Re;
                result.Type = reflectionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                result.Lambda = Krefl.Sample(lambda);

            }
            else
            {
                (wi) = transDir;
                result.Pdf = (1f - P) / Tr;
                result.Type = transmitionSpecularBounce ? BrdfType.Specular : BrdfType.Glossy;
                result.Lambda = Krefrct.Sample(lambda);
            }
            result.Wi = wi;
        }
    };
}